﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Obra.Sync;
using Obra.Api;
using System.ServiceModel.Channels;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using System.Diagnostics;
using System.ComponentModel.Design;
using System.IO;

namespace Obra.CommandLineInterface
{
    public sealed class Application :
        IServiceContainer,
        IServiceProvider
    {
        public const string LOCAL_SYNC_FILE_NAME = "local.obr";
        public const string REMOTE_SYNC_FILE_NAME = "remote.obr";

        private readonly SyncOptions syncOptions;

        private readonly ServiceContainer services;

        private string directory;

        private string localFileName;

        private string remoteFileName;

        /// <summary>
        /// c-tor
        /// </summary>
        public Application()
        {
            this.syncOptions = new SyncOptions();
            this.services = new ServiceContainer();
        }

        public string Directory__Test
        {
            get { return this.directory; }
        }

        public string LocalFileName__Test
        {
            get { return this.localFileName; }
        }

        public string RemoteFileName__Test
        {
            get { return this.remoteFileName; }
        }

        /// <summary>
        /// Validate user input.
        /// </summary>
        private void ValidateUserInput(
            IArgumentParserService service)
        {
            if (String.IsNullOrEmpty(service.UserName) ||
                String.IsNullOrEmpty(service.Password))
            {
                throw new ArgumentException();
            }

            this.directory = service.Directory;

            if (String.IsNullOrEmpty(this.directory))
            {
                this.directory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
            }

            if (!Directory.Exists(this.directory))
            {
                throw new ArgumentException();
            }
        }

        /// <summary>
        /// Copy the specified file to a temporary area.
        /// </summary>
        /// <returns>
        /// The temporary path the file was copied to.
        /// </returns>
        private string CopyFileToTemporaryDirectory(
            string path)
        {
            string temporaryFileName = Path.Combine(
                Path.GetTempPath(),
                Path.GetRandomFileName());

            // If this is the first run, there is no file to copy.
            if (File.Exists(path))
            {
                File.Copy(
                    path,
                    temporaryFileName);
            }

            return temporaryFileName;
        }

        /// <summary>
        /// Get the path to the synchronization file.
        /// </summary>
        /// <remarks>
        /// <para>
        /// If the user specified a dry run the synchronization data files are 
        /// copied to a temporary area, and the operated on.
        /// </para>
        /// </remarks>
        private string GetFileNamePath(
            IArgumentParserService service,
            string fileName)
        {
            string path = Path.Combine(
                this.directory,
                fileName);

            if (service.DryRun)
            {
                return this.CopyFileToTemporaryDirectory(path);
            }

            return path;
        }

        /// <summary>
        /// Set the file system service to use for synchronization.
        /// </summary>
        private ISyncService SetFileSystemService(
            IArgumentParserService argumentParserService)
        {
            ISyncService service = new FileSystemSyncServiceProvider(
                this.directory,
                this.syncOptions,
                this.GetService<IFileSystemService>());

            if (argumentParserService.DryRun)
            {
                service = new DryRunSyncServiceProvider(
                    service);
            }

            return service;
        }

        /// <summary>
        /// Set the media service to use for synchronization.
        /// </summary>
        private ISyncService SetMediaService(
            IArgumentParserService argumentParserService)
        {
            ISyncService service = new MediaServiceSyncServiceProvider(
                argumentParserService.UserName,
                argumentParserService.Password,
                this.GetService<IMediaService>());

            if (argumentParserService.DryRun)
            {
                service = new DryRunSyncServiceProvider(
                    service);
            }

            return service;
        }

        public void Parse(string[] args)
        {
            IArgumentParserService service = this.GetService<IArgumentParserService>();
            service.Parse(args);

            this.ValidateUserInput(service);
        }

        public void Execute()
        {
            IArgumentParserService service = this.GetService<IArgumentParserService>();

            ISyncService filesystemSyncService = this.SetFileSystemService(
                service);

            ISyncService mediaSyncService = this.SetMediaService(
                service);

            this.localFileName = this.GetFileNamePath(
                service,
                Application.LOCAL_SYNC_FILE_NAME);

            this.remoteFileName = this.GetFileNamePath(
                service,
                Application.REMOTE_SYNC_FILE_NAME);

            using (SyncProvider localSyncProvider = new SyncProvider(
                filesystemSyncService,
                this.localFileName))
            using (SyncProvider remoteSyncProvider = new SyncProvider(
                mediaSyncService,
                this.remoteFileName))
            {
                Orchestrator orchestrator = new Orchestrator(
                    localSyncProvider,
                    remoteSyncProvider);

                orchestrator.Upload();
            }
        }

        #region IServiceContainer Members

        /// <summary>
        /// Adds the specified service to the service container, and optionally promotes the service to parent service containers.
        /// </summary>
        /// <param name="serviceType">The type of service to add.</param>
        /// <param name="callback">A callback object that is used to create the service. This allows a service to be declared as available, but delays the creation of the object until the service is requested.</param>
        /// <param name="promote">true to promote this request to any parent service containers; otherwise, false.</param>
        public void AddService(Type serviceType, ServiceCreatorCallback callback, bool promote)
        {
            this.services.AddService(serviceType, callback, promote);
        }

        /// <summary>
        /// Adds the specified service to the service container.
        /// </summary>
        /// <param name="serviceType">The type of service to add.</param>
        /// <param name="callback">A callback object that is used to create the service. This allows a service to be declared as available, but delays the creation of the object until the service is requested.</param>
        public void AddService(Type serviceType, ServiceCreatorCallback callback)
        {
            this.services.AddService(serviceType, callback);
        }

        /// <summary>
        /// Adds the specified service to the service container, and optionally promotes the service to any parent service containers.
        /// </summary>
        /// <param name="serviceType">The type of service to add.</param>
        /// <param name="serviceInstance">An instance of the service type to add. This object must implement or inherit from the type indicated by the <paramref name="serviceType"/> parameter.</param>
        /// <param name="promote">true to promote this request to any parent service containers; otherwise, false.</param>
        public void AddService(Type serviceType, object serviceInstance, bool promote)
        {
            this.services.AddService(serviceType, serviceInstance, promote);
        }

        /// <summary>
        /// Adds the specified service to the service container.
        /// </summary>
        /// <param name="serviceType">The type of service to add.</param>
        /// <param name="serviceInstance">An instance of the service type to add. This object must implement or inherit from the type indicated by the <paramref name="serviceType"/> parameter.</param>
        public void AddService(Type serviceType, object serviceInstance)
        {
            this.services.AddService(serviceType, serviceInstance);
        }

        /// <summary>
        /// Removes the specified service type from the service container, and optionally promotes the service to parent service containers.
        /// </summary>
        /// <param name="serviceType">The type of service to remove.</param>
        /// <param name="promote">true to promote this request to any parent service containers; otherwise, false.</param>
        public void RemoveService(Type serviceType, bool promote)
        {
            this.services.RemoveService(serviceType, promote);
        }

        /// <summary>
        /// Removes the specified service type from the service container.
        /// </summary>
        /// <param name="serviceType">The type of service to remove.</param>
        public void RemoveService(Type serviceType)
        {
            this.services.RemoveService(serviceType);
        }

        #endregion

        #region IServiceProvider Members

        /// <summary>
        /// Gets the service object of the specified type.
        /// </summary>
        /// <param name="serviceType">An object that specifies the type of service object to get.</param>
        /// <returns>
        /// A service object of type <paramref name="serviceType"/>.
        /// -or-
        /// null if there is no service object of type <paramref name="serviceType"/>.
        /// </returns>
        public object GetService(Type serviceType)
        {
            return this.services.GetService(serviceType);
        }

        public TService GetService<TService>()
        {
            return (TService)this.GetService(typeof(TService));
        }

        #endregion
    }
}
